<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<bindings id="dateTimePopupBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:html="http://www.w3.org/1999/xhtml"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">
  <binding id="datetime-popup"
           extends="chrome://global/content/bindings/popup.xml#arrowpanel">
    <resources>
      <stylesheet src="chrome://global/skin/datetimepopup.css"/>
    </resources>
    <implementation>
      <field name="dateTimePopupFrame">
        this.querySelector("#dateTimePopupFrame");
      </field>
      <field name="TIME_PICKER_WIDTH" readonly="true">"12em"</field>
      <field name="TIME_PICKER_HEIGHT" readonly="true">"21em"</field>
      <field name="DATE_PICKER_WIDTH" readonly="true">"23.1em"</field>
      <field name="DATE_PICKER_HEIGHT" readonly="true">"20.7em"</field>
      <constructor><![CDATA[
        this.l10n = {};
        const mozIntl = Components.classes["@mozilla.org/mozintl;1"]
                          .getService(Components.interfaces.mozIMozIntl);
        mozIntl.addGetCalendarInfo(this.l10n);
        mozIntl.addGetDisplayNames(this.l10n);
        // Notify DateTimePickerHelper.jsm that binding is ready.
        this.dispatchEvent(new CustomEvent("DateTimePickerBindingReady"));
      ]]></constructor>
      <method name="openPicker">
        <parameter name="type"/>
        <parameter name="anchor"/>
        <parameter name="detail"/>
        <body><![CDATA[
          this.type = type;
          this.pickerState = {};
          // TODO: Resize picker according to content zoom level
          this.style.fontSize = "10px";
          switch (type) {
            case "time": {
              this.detail = detail;
              this.dateTimePopupFrame.addEventListener("load", this, true);
              this.dateTimePopupFrame.setAttribute("src", "chrome://global/content/timepicker.xhtml");
              this.dateTimePopupFrame.style.width = this.TIME_PICKER_WIDTH;
              this.dateTimePopupFrame.style.height = this.TIME_PICKER_HEIGHT;
              break;
            }
            case "date": {
              this.detail = detail;
              this.dateTimePopupFrame.addEventListener("load", this, true);
              this.dateTimePopupFrame.setAttribute("src", "chrome://global/content/datepicker.xhtml");
              this.dateTimePopupFrame.style.width = this.DATE_PICKER_WIDTH;
              this.dateTimePopupFrame.style.height = this.DATE_PICKER_HEIGHT;
              break;
            }
          }
          this.hidden = false;
          this.openPopup(anchor, "after_start", 0, 0);
        ]]></body>
      </method>
      <method name="closePicker">
        <body><![CDATA[
          this.setInputBoxValue(true);
          this.pickerState = {};
          this.type = undefined;
          this.dateTimePopupFrame.removeEventListener("load", this, true);
          this.dateTimePopupFrame.contentDocument.removeEventListener("message", this, false);
          this.dateTimePopupFrame.setAttribute("src", "");
          this.hidden = true;
        ]]></body>
      </method>
      <method name="setPopupValue">
        <parameter name="data"/>
        <body><![CDATA[
          switch (this.type) {
            case "time": {
              this.postMessageToPicker({
                name: "PickerSetValue",
                detail: data.value
              });
              break;
            }
            case "date": {
              const { year, month, day } = data.value;
              this.postMessageToPicker({
                name: "PickerSetValue",
                detail: {
                  year,
                  // Month value from input box starts from 1 instead of 0
                  month: month == undefined ? undefined : month - 1,
                  day
                }
              });
              break;
            }
          }
        ]]></body>
      </method>
      <method name="initPicker">
        <parameter name="detail"/>
        <body><![CDATA[
          const locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global");

          switch (this.type) {
            case "time": {
              const { hour, minute } = detail.value;
              const format = detail.format || "12";

              this.postMessageToPicker({
                name: "PickerInit",
                detail: {
                  hour,
                  minute,
                  format,
                  locale,
                  min: detail.min,
                  max: detail.max,
                  step: detail.step,
                }
              });
              break;
            }
            case "date": {
              const { year, month, day } = detail.value;
              const { firstDayOfWeek, weekends } =
                this.getCalendarInfo(locale);
              const monthStrings = this.getDisplayNames(
                locale, [
                  "dates/gregorian/months/january",
                  "dates/gregorian/months/february",
                  "dates/gregorian/months/march",
                  "dates/gregorian/months/april",
                  "dates/gregorian/months/may",
                  "dates/gregorian/months/june",
                  "dates/gregorian/months/july",
                  "dates/gregorian/months/august",
                  "dates/gregorian/months/september",
                  "dates/gregorian/months/october",
                  "dates/gregorian/months/november",
                  "dates/gregorian/months/december",
                ], "short");
              const weekdayStrings = this.getDisplayNames(
                locale, [
                  "dates/gregorian/weekdays/sunday",
                  "dates/gregorian/weekdays/monday",
                  "dates/gregorian/weekdays/tuesday",
                  "dates/gregorian/weekdays/wednesday",
                  "dates/gregorian/weekdays/thursday",
                  "dates/gregorian/weekdays/friday",
                  "dates/gregorian/weekdays/saturday",
                ], "short");

              this.postMessageToPicker({
                name: "PickerInit",
                detail: {
                  year,
                  // Month value from input box starts from 1 instead of 0
                  month: month == undefined ? undefined : month - 1,
                  day,
                  firstDayOfWeek,
                  weekends,
                  monthStrings,
                  weekdayStrings,
                  locale,
                  min: detail.min,
                  max: detail.max,
                  step: detail.step,
                  stepBase: detail.stepBase,
                }
              });
              break;
            }
          }
        ]]></body>
      </method>
      <method name="setInputBoxValue">
        <parameter name="passAllValues"/>
        <body><![CDATA[
          /**
           * @param {Boolean} passAllValues: Pass spinner values regardless if they've been set/changed or not
           */
          switch (this.type) {
            case "time": {
              const { hour, minute, isHourSet, isMinuteSet, isDayPeriodSet } = this.pickerState;
              const isAnyValueSet = isHourSet || isMinuteSet || isDayPeriodSet;
              if (passAllValues && isAnyValueSet) {
                this.sendPickerValueChanged({ hour, minute });
              } else {
                this.sendPickerValueChanged({
                  hour: isHourSet || isDayPeriodSet ? hour : undefined,
                  minute: isMinuteSet ? minute : undefined
                });
              }
              break;
            }
            case "date": {
              this.sendPickerValueChanged(this.pickerState);
              break;
            }
          }
        ]]></body>
      </method>
      <method name="sendPickerValueChanged">
        <parameter name="value"/>
        <body><![CDATA[
          switch (this.type) {
            case "time": {
              this.dispatchEvent(new CustomEvent("DateTimePickerValueChanged", {
                detail: {
                  hour: value.hour,
                  minute: value.minute
                }
              }));
              break;
            }
            case "date": {
              this.dispatchEvent(new CustomEvent("DateTimePickerValueChanged", {
                detail: {
                  year: value.year,
                  // Month value from input box starts from 1 instead of 0
                  month: value.month == undefined ? undefined : value.month + 1,
                  day: value.day
                }
              }));
              break;
            }
          }
        ]]></body>
      </method>
      <method name="getCalendarInfo">
        <parameter name="locale"/>
        <body><![CDATA[
          const calendarInfo = this.l10n.getCalendarInfo(locale);

          // Day of week from calendarInfo starts from 1 as Sunday to 7 as Saturday,
          // so they need to be mapped to JavaScript convention with 0 as Sunday
          // and 6 as Saturday
          let firstDayOfWeek = calendarInfo.firstDayOfWeek - 1,
              weekendStart = calendarInfo.weekendStart - 1,
              weekendEnd = calendarInfo.weekendEnd - 1;

          let weekends = [];

          // Make sure weekendEnd is greater than weekendStart
          if (weekendEnd < weekendStart) {
            weekendEnd += 7;
          }

          // We get the weekends by incrementing weekendStart up to weekendEnd.
          // If the start and end is the same day, then weekends only has one day.
          for (let day = weekendStart; day <= weekendEnd; day++) {
            weekends.push(day % 7);
          }

          return {
            firstDayOfWeek,
            weekends
          }
        ]]></body>
      </method>
      <method name="getDisplayNames">
        <parameter name="locale"/>
        <parameter name="keys"/>
        <parameter name="style"/>
        <body><![CDATA[
          const displayNames = this.l10n.getDisplayNames(locale, {keys, style});
          return keys.map(key => displayNames.values[key]);
        ]]></body>
      </method>
      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body><![CDATA[
          switch (aEvent.type) {
            case "load": {
              this.initPicker(this.detail);
              this.dateTimePopupFrame.contentWindow.addEventListener("message", this, false);
              break;
            }
            case "message": {
              this.handleMessage(aEvent);
              break;
            }
          }
        ]]></body>
      </method>
      <method name="handleMessage">
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (!this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal) {
            return;
          }

          switch (aEvent.data.name) {
            case "PickerPopupChanged": {
              this.pickerState = aEvent.data.detail;
              this.setInputBoxValue();
              break;
            }
            case "ClosePopup": {
              this.hidePopup();
              this.closePicker();
              break;
            }
          }
        ]]></body>
      </method>
      <method name="postMessageToPicker">
        <parameter name="data"/>
        <body><![CDATA[
          if (this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal) {
            this.dateTimePopupFrame.contentWindow.postMessage(data, "*");
          }
        ]]></body>
      </method>

    </implementation>
  </binding>
</bindings>
